#!/usr/bin/perl

=head1 NAME

Swignition::DataModel::Model - Container for one or more graphs.

=cut

package Swignition::DataModel::Model;

use Carp;
use URI::URL;

use constant TRUE      =>  1;
use constant FALSE     =>  0;
use strict;

our $VERSION = '0.1';

=head1 CONSTRUCTORS

=over

=item $model = Swignition::DataModel::Model->new

Create a new model. You only want one model.

=cut

sub new
{
	my $class = shift;
	my $model = shift;
	
	my $this = {
		'_' => {
			'usage' => {
				'has_node'     => [],   # list of nodes
				'has_graph'    => [],   # list of graphs
			},
			'fast_find' => {},
			'combined' => 1,
			'named_nodes' => {},
		},
	};
	
	bless $this, $class;
	
	$this->{'_'}->{'combined'} = Swignition::DataModel::Graph->new($this);
	
	# Tell the combined graph that it is the combined graph!
	$this->combined_graph->{'_'}->{'combined'} = TRUE;
	
	return $this;
}

=back

=head1 PUBLIC METHODS

=over

=item $node->combined_graph

Returns the big combined graph.

=cut

sub combined_graph
{
	my $this = shift;
	return $this->{'_'}->{'combined'};
}

=item $model->add_node($node)

Adds a node to the model.

=cut

sub add_node
{
	my $this = shift;
	my $node = shift;
	
	return $this if ($node->{'_'}->{'in_model'} == $this);
	
	# Look for potentials to merge with
	my @mergeWith;
	
	# For each URI the new node has...
	foreach my $nodeuri ($node->uris)
	{
		my $existing = $this->{'_'}->{'fast_find'}->{$nodeuri};
		
		# If there's already a node with that URI, but it's
		# not the same as this one...
		if (defined $existing && !$node->same_as($existing))
		{
			# Check if we already know about it
			my $knowIt = 0;
			foreach my $m (@mergeWith)
				{ $knowIt = 1 if $m->same_as($existing); }
				
			# If we don't then add it to the merge queue.
			push @mergeWith, $existing unless $knowIt;
		}
	}
	
	# Do merges
	foreach my $existing (@mergeWith)
	{
		$node->merge_with($existing);
	}
	
	# Make node available in fast find.
	foreach my $nodeuri ($node->uris)
	{
		$this->{'_'}->{'fast_find'}->{$nodeuri} = $node;
	}
	
	# This will occasionally create duplicates which should be deleted really.
	push @{ $this->{'_'}->{'usage'}->{'has_node'} }, $node;
	
	# Remember that the node is in the model to avoid re-adding it.
	$node->{'_'}->{'in_model'} = $this;
	
	return $this;
}

=item $model->nodes

Get a list of all nodes used in this model.

=cut

sub nodes
{
	my $this = shift;
	return $this->{'_'}->{'usage'}->{'has_node'};
}

=item $model->graphs

Get a list of all graphs used in this model.

=cut

sub graphs
{
	my $this = shift;
	return $this->{'_'}->{'usage'}->{'has_graph'};
}


=item $node = $model->lookup_node($uri, $mint)

Find the node representing $uri. If no such node is found and $mint is true
then will create a node to represent the URI.

=cut

sub lookup_node
{
	my $this = shift;
	my $uri  = shift;
	my $mint = shift || 0;
	
	# Fast find will hopefully find the node pretty quickly.
	if (defined $this->{'_'}->{'fast_find'}->{$uri})
	{
		my $possible = $this->{'_'}->{'fast_find'}->{$uri};
		return $possible if ($possible->answers_to($uri));
	}
	
	# Otherwise check each node.
	foreach my $possible (@{$this->nodes})
	{
		next unless $possible->answers_to($uri);
		$this->{'_'}->{'fast_find'}->{$uri} = $possible;
		return $possible;
	}
	
	# If still not found, we need to mint a new node.
	if ($mint)
	{
		my $rv = Swignition::DataModel::Resource->new($this, $uri);
		$this->{'_'}->{'fast_find'}->{$uri} = $rv;
		return $rv;
	}
	
	return undef;
}

=item $accepted_name = $model->register_node_name($node, $suggested_name)

Create a name for a (usually blank) node which is unique within the model.

=cut

sub register_node_name
{
	my $this = shift;
	my $node = shift;
	my $name = shift || 'BlankNode';
	
	$name =~ s/^_://;
	
	# Never allow a node to register itself with name matching /^n\d*/ because
	# we use that internally to generate unique names for blank nodes.
	$name =~ s/^(n)(\d*)/x\1\2/i;
	
	if (defined $this->{'_'}->{'named_nodes'}->{$name}
	&&  $node->same_as($this->{'_'}->{'named_nodes'}->{$name}))
	{
		return $name;
	}
	
	if (defined $this->{'_'}->{'named_nodes'}->{$name})
	{
		my $format = '%s_%03d';
		my $number = 0;
		my $stump  = $name;
		if ($name =~ /^(.+?)[_-]?(\d+)$/)
		{
			$stump  = $1;
			$number = $2;
		}
		
		while (defined $this->{'_'}->{'named_nodes'}->{$name})
		{
			$number++;
			$name = sprintf($format, $stump, $number);
		}
	}
	
	$this->{'_'}->{'named_nodes'}->{$name} = $node;
	return $name;
}

=back

=cut


1;

